/*****************************************************************************
 * hal_linux_uart.c
 *
 * Copyright (C) 2019 Jeasonvor <1101627719@qq.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 ****************************************************************************/

/*****************************************************************************
 * Included Files
 ****************************************************************************/

#include "plum_api.h"
#include "hal/source/hal_linux/hal_linux.h"

/*****************************************************************************
 * Trace Definitions
 ****************************************************************************/

#define LOG_RUN_LEVEL LOG_LEVEL_INFO
#define LOG_MODULE    "HAL.UART"

/*****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/*****************************************************************************
 * Private Types
 ****************************************************************************/

typedef struct {
    plum_u32         id;
    int              fd;
    struct list_head list;
} hal_uart_t;

/*****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/*****************************************************************************
 * Private Data
 ****************************************************************************/

PLUM_PRIVATE hal_uart_t uart_head;

/*****************************************************************************
 * Public Data
 ****************************************************************************/

/*****************************************************************************
 * Private Functions
 ****************************************************************************/

PLUM_PRIVATE
plum_void hal_uart_path(plum_u32 id, plum_s8 *path, plum_u32 path_len)
{
    plum_u32 idx = PLUM_HAL_UART_IDX(id);

    if (idx < 100) {
        snprintf((char *)path, path_len, "/dev/ttyS%d", idx);
    }
    else {
        snprintf((char *)path, path_len, "/dev/ttyUSB%d", idx - 100);
    }
}

PLUM_PRIVATE
hal_uart_t *hal_uart_apply(plum_u32 id, int fd)
{
    if (!uart_head.list.next) {
        INIT_LIST_HEAD(&uart_head.list);
    }

    hal_uart_t *node = (hal_uart_t *)plum_kal_mem_malloc(sizeof(hal_uart_t));
    if (!node) {
        LOG_E("uart malloc err");
    }
    else {
        memset(node, 0, sizeof(hal_uart_t));
        node->id = id;
        node->fd = fd;
    }

    return (node);
}

PLUM_PRIVATE
hal_uart_t *hal_uart_find(plum_u32 id)
{
    hal_uart_t *      node = NULL;
    struct list_head *pos;
    list_for_each(pos, &uart_head.list)
    {
        node = list_entry(pos, hal_uart_t, list);
        if (node->id == id) {
            return (node);
        }
    }
    return (plum_null);
}

PLUM_PRIVATE
plum_s32 hal_uart_insert(hal_uart_t *node)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!node) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        list_add(&node->list, &uart_head.list);
    } while (0);

    return (rc);
}

/*****************************************************************************
 * Public Functions
 ****************************************************************************/

PLUM_PUBLIC
plum_s32 plum_hal_uart_init(plum_u32 id, plum_hal_uart_cof_t *config)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        //       LOG_D("this is plum_hal_uart_init");
        plum_s8 path[16] = {0};
        hal_uart_path(id, path, sizeof(path));
        LOG_D("path:%s", path);

        int fd = open((const char *)path, O_RDWR | O_NOCTTY);
        if (fd < 0) {
            LOG_E("open [%s] err,rc:%d", path, fd);
            perror("reason:");
            rc = PLUM_ECODE_EIO;
            break;
        }

        tcflush(fd, TCIOFLUSH);
        struct termios io = {0};

        // iflag
        {
            io.c_iflag = IGNBRK;
            if (config->parity != PLUM_HAL_UART_PARITY_NONE) {
                io.c_iflag |= INPCK;

                if (config->databit != PLUM_HAL_UART_DATABIT_8) {
                    io.c_iflag |= ISTRIP;
                }
            }
        }

        // oflag
        {
            io.c_oflag = 0;
        }

        // lflag
        {
            io.c_lflag = 0;
        }

        // cflag
        {
            io.c_cflag = CLOCAL | CREAD;

            switch (config->databit) {
                case PLUM_HAL_UART_DATABIT_5:
                    io.c_cflag |= CS5;
                    break;
                case PLUM_HAL_UART_DATABIT_6:
                    io.c_cflag |= CS6;
                    break;
                case PLUM_HAL_UART_DATABIT_7:
                    io.c_cflag |= CS7;
                    break;
                case PLUM_HAL_UART_DATABIT_8:
                    io.c_cflag |= CS8;
                    break;
                default:
                    break;
            }

            switch (config->parity) {
                case PLUM_HAL_UART_PARITY_NONE:
                    break;
                case PLUM_HAL_UART_PARITY_EVEN:
                    io.c_cflag |= PARENB;
                    io.c_cflag |= PARODD;
                    break;
                case PLUM_HAL_UART_PARITY_ODD:
                    io.c_cflag |= PARENB;
                    break;
                default:
                    break;
            }

            switch (config->baudrate) {
                case 2400:
                    cfsetispeed(&io, B2400);
                    cfsetospeed(&io, B2400);
                    break;
                case 4800:
                    cfsetispeed(&io, B4800);
                    cfsetospeed(&io, B4800);
                    break;
                case 9600:
                    cfsetispeed(&io, B9600);
                    cfsetospeed(&io, B9600);
                    break;
                case 19200:
                    cfsetispeed(&io, B19200);
                    cfsetospeed(&io, B19200);
                    break;
                case 57600:
                    cfsetispeed(&io, B57600);
                    cfsetospeed(&io, B57600);
                    break;
                case 115200:
                    cfsetispeed(&io, B115200);
                    cfsetospeed(&io, B115200);
                    break;
                default:
                    break;
            }

            switch (config->stopbit) {
                case PLUM_HAL_UART_STOPBIT_1:
                    io.c_cflag &= ~CSTOPB;
                    break;
                case PLUM_HAL_UART_STOPBIT_2:
                    io.c_cflag |= ~CSTOPB;
                    break;
                default:
                    break;
            }
        }

        rc = tcsetattr(fd, TCSANOW, &io);
        if (rc) {
            LOG_E("tcsetattr err,rc:%d", rc);
            break;
        }

        hal_uart_t *node = hal_uart_apply(id, fd);
        if (!node) {
            rc = PLUM_ECODE_EMEM;
            LOG_E("hal_uart_apply err,rc:%d", rc);
            break;
        }

        rc = hal_uart_insert(node);
        if (rc) {
            LOG_E("hal_uart_insert err,rc:%d", rc);
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_hal_uart_send(plum_u32 id, const void *data, plum_u16 cnt)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_UART(id)) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        hal_uart_t *node = hal_uart_find(id);
        if (!node) {
            LOG_E("not init the uart,id:%d", id);
            rc = PLUM_ECODE_EIO;
            break;
        }

        rc = write(node->fd, data, cnt);
        if (rc < 0) {
            LOG_E("uart write err,rc:%d", rc);
            rc = PLUM_ECODE_EIO;
            break;
        }
    } while (0);

    return (rc);
}

PLUM_PUBLIC
plum_s32 plum_hal_uart_recv(plum_u32 id, void *data, plum_u16 size,
                            plum_u32 timeout)
{
    plum_s32 rc = PLUM_ECODE_OK;

    do {
        if (!PLUM_HAL_ID_IS_UART(id)) {
            rc = PLUM_ECODE_EPARA;
            break;
        }

        hal_uart_t *node = hal_uart_find(id);
        if (!node) {
            LOG_E("not init the uart,id:%d", id);
            rc = PLUM_ECODE_EIO;
            break;
        }

        uint8_t *buf        = (uint8_t *)data;
        uint32_t bytes_left = size;
        uint32_t bytes_read = 0;

        struct timeval time;
        time.tv_sec  = timeout / 1000;
        time.tv_usec = (timeout % 1000) * 1000;

        fd_set rfd;
        do {
            FD_ZERO(&rfd);
            FD_SET(node->fd, &rfd);

            rc = select(node->fd + 1, &rfd, NULL, NULL, &time);
            if (rc < 0) {
                LOG_E("uart select err,rc:%d", rc);
                rc = PLUM_ECODE_EIO;
                break;
            }
            else if (rc == 0) {
                rc = PLUM_ECODE_TIMEOUT;
                break;
            }
            else {
                rc = read(node->fd, buf + bytes_read, bytes_left);
                if (rc < 0) {
                    LOG_E("uart read err,rc:%d", rc);
                    rc = PLUM_ECODE_EIO;
                    break;
                }
                bytes_read += rc;
                bytes_left -= rc;
            }
        } while (bytes_left);

        rc = bytes_read;
    } while (0);

    return (rc);
}

/****************************************************************************/
/*                                                                          */
/*  End of file.                                                            */
/*                                                                          */
/****************************************************************************/
